#ifndef FHE_VAL_H
#define FHE_VAL_H

#include <fhe/core/Util.h>
#include <string>
#include <typeinfo>

namespace fhe
{
  namespace core
  {
    class FHE_API Val
    {
    public:
      Val();
      Val( const Val& val );
      Val& operator=( const Val& val );
      ~Val();
      
      template <typename T>
      Val( T t )
        : data_( new Data<T>( t ) )
      {
      }
      
      template <typename T>
      bool is() const
      {
        return data_ && data_->as<T>();
      }
      
      template <typename T>
      T get() const
      {
        FHE_ASSERT( data_ && data_->as<T>() );
        return data_->as<T>()->get();
      }
      
      template <typename T>
      bool tryGet( T& t ) const
      {
        if ( data_ && data_->as<T>() )
        {
          t = data_->as<T>()->get();
          return true;
        }
        else
        {
          return false;
        }
      }
      
      template <typename T>
      void set( T t )
      {
        if ( data_ && data_->as<T>() )
        {
          data_->as<T>()->set( t );
        }
        else
        {
          clear();
          data_ = new Data<T>( t );
        }
      }
      
      bool empty() const;
      
      void clear();
      
      std::string type() const;
      
      const std::type_info& typeInfo() const;
      
    private:
      template <typename T>
      class Data;
      
      class IData
      {
      public:
        template <typename T>
        Data<T>* as()
        {
          return dynamic_cast<Data<T>*>( this );
        }
        
        virtual IData* clone() = 0;
        
        virtual const std::type_info& typeInfo() const = 0;
      };
      
      template <typename T>
      class Data : public IData
      {
      public:
        Data( T t )
          : t_( t )
        {
        }
        
        IData* clone()
        {
          return new Data<T>( t_ );
        }
        
        T get() const
        {
          return t_;
        }
        
        void set( T t )
        {
          t_ = t;
        }
        
        const std::type_info& typeInfo() const
        {
          return typeid(T);
        }
        
      private:
        T t_;
      };
      
      IData* data_;
    };
  }
}

#endif
